#include "record_desktop_gdi.h"
#include "../util/common.h"
#include "error_define.h"
#include "video.h"
#include <thread>


record_desktop_gdi::record_desktop_gdi()
{
	_imgBuf = nullptr;
	_draw_cursor = true;

	_hdc = NULL;
	_bmp = NULL;
	_bmp_old = NULL;
	_ci = { 0 };
}

record_desktop_gdi::~record_desktop_gdi()
{
	stop();
	clean_up();
}

int record_desktop_gdi::init(const Rect& rect, const int fps)
{
	int error = AE_NO;
	if (_inited == true) {
		return error;
	}

	_fps = fps;
	_rect = rect;


	do {
		_imgBuf = (FRAME_PKG*)malloc(sizeof(FRAME_PKG));
		_imgBuf->w = rect.width();
		_imgBuf->h = rect.height();
		_imgBuf->size = (rect.width() * 32 + 31) / 32 * rect.height() * 4;
		_imgBuf->img = new uint8_t[_imgBuf->size];

		_inited = true;
	} while (0);

	return error;
}

int record_desktop_gdi::start()
{
	if (_running == true) {
		//al_warn("record desktop gdi is already running");
		return AE_NO;
	}

	if (_inited == false) {
		return AE_NEED_INIT;
	}

	_running = true;
	_thread = std::thread(std::bind(&record_desktop_gdi::record_func, this));

	return AE_NO;
}

int record_desktop_gdi::pause()
{
	_paused = true;
	return AE_NO;
}

int record_desktop_gdi::resume()
{
	_paused = false;
	return AE_NO;
}

int record_desktop_gdi::stop()
{
	_running = false;
	if (_thread.joinable())
		_thread.join();

	return AE_NO;
}

void record_desktop_gdi::clean_up()
{
	_inited = false;

	if (_imgBuf) {
		delete[] _imgBuf->img;
		free(_imgBuf);
	}
	_imgBuf = nullptr;
}

void record_desktop_gdi::draw_cursor(HDC hdc)
{
	if (!(_ci.flags & CURSOR_SHOWING))
		return;

	//is cursor in the tartet zone
	if (_ci.ptScreenPos.x < _rect.left() ||
		_ci.ptScreenPos.x > _rect.right() ||
		_ci.ptScreenPos.y < _rect.top() ||
		_ci.ptScreenPos.y > _rect.bottom()
		)
		return;

	HICON icon;
	ICONINFO ii;

	icon = CopyIcon(_ci.hCursor);
	if (!icon)
		return;

	int dstx = 0, dsty = 0;
	dstx = abs(_ci.ptScreenPos.x - _rect.left());
	dsty = abs(_ci.ptScreenPos.y - _rect.top());

	if (GetIconInfo(icon, &ii)) {
		POINT pos;
		DrawIconEx(hdc, dstx, dsty, icon, 0, 0, 0, NULL, DI_NORMAL);

		DeleteObject(ii.hbmColor);
		DeleteObject(ii.hbmMask);
	}

	DestroyIcon(icon);
}

int record_desktop_gdi::do_record()
{
	//int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
	//int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
	HDC hdc_screen = NULL, hdc_mem = NULL;
	HBITMAP hbm_mem = NULL;

	int error = AE_ERROR;

	do {

		hdc_screen = GetWindowDC(NULL);
		if (!hdc_screen) {
			//al_error("get window dc failed:%lu", GetLastError());
			error = AE_GDI_GET_DC_FAILED;
			break;
		}

		hdc_mem = CreateCompatibleDC(hdc_screen);
		if (!hdc_mem) {
			//al_error("create compatible dc failed:%lu", GetLastError());
			error = AE_GDI_CREATE_DC_FAILED;
			break;
		}

		hbm_mem = CreateCompatibleBitmap(hdc_screen, _imgBuf->w, _imgBuf->h);
		if (!hbm_mem) {
			//al_error("create compatible bitmap failed:%lu", GetLastError());
			error = AE_GDI_CREATE_BMP_FAILED;
			break;
		}

		SelectObject(hdc_mem, hbm_mem);

		//must have CAPTUREBLT falg,otherwise some layered window can not be captured
		if (!BitBlt(hdc_mem, 0, 0, _imgBuf->w, _imgBuf->h, hdc_screen, _rect.left(), _rect.top(), SRCCOPY | CAPTUREBLT)) {
			//al_error("bitblt data failed:%lu", GetLastError());
			//error = AE_GDI_BITBLT_FAILED;
			//administrator UAC will trigger invalid handle error
			break;
		}

		memset(&_ci, 0, sizeof(CURSORINFO));
		_ci.cbSize = sizeof(CURSORINFO);
		if (GetCursorInfo(&_ci)) {
			draw_cursor(hdc_mem);
		}

		BITMAPINFOHEADER   bi;

		bi.biSize = sizeof(BITMAPINFOHEADER);
		bi.biWidth = _imgBuf->w;
		bi.biHeight = _imgBuf->h* (-1);
		bi.biPlanes = 1;
		bi.biBitCount = 32;//should get from system color bits
		bi.biCompression = BI_RGB;
		bi.biSizeImage = 0;
		bi.biXPelsPerMeter = 0;
		bi.biYPelsPerMeter = 0;
		bi.biClrUsed = 0;
		bi.biClrImportant = 0;

		//scan colors by line order
		int ret = GetDIBits(hdc_mem, hbm_mem, 0,  _imgBuf->h, _imgBuf->img, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
		if (ret <= 0 || ret == ERROR_INVALID_PARAMETER) {
			//al_error("get dibits failed:%lu", GetLastError());
			error = AE_GDI_GET_DIBITS_FAILED;
			break;
		}

#if 0
		//save bmp to test
		BITMAPFILEHEADER bf;
		bf.bfType = 0x4d42;
		bf.bfReserved1 = 0;
		bf.bfReserved2 = 0;
		bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
		bf.bfSize = bf.bfOffBits + _width * _height * 4;

		FILE* fp = fopen("..\\..\\save.bmp", "wb+");

		fwrite(&bf, 1, sizeof(bf), fp);
		fwrite(&bi, 1, sizeof(bi), fp);
		fwrite(_buffer, 1, _buffer_size, fp);

		fflush(fp);
		fclose(fp);
#endif
		error = AE_NO;
	} while (0);

	if (hbm_mem)
		DeleteObject(hbm_mem);

	if (hdc_mem)
		DeleteObject(hdc_mem);

	if (hdc_screen)
		ReleaseDC(NULL, hdc_screen);

	return  AE_NO;
}

void record_desktop_gdi::record_func()
{
	int64_t pre_pts = 0;
	int64_t dur = AV_TIME_BASE / _fps;

	int ret = AE_NO;
	while (_running)
	{
		ret = do_record();
		if (ret != AE_NO) {
			//todo hdd
			//if (_on_error) _on_error(ret);
			break;
		}


		_on_data(_imgBuf);

		time_t pts =av_time();
		do_sleep(dur, pre_pts, pts);

		pre_pts = pts;
	}
}
